﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Schema;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
namespace WITD
{
    /// <summary>
    /// Supports operations on work items
    /// </summary>
    class WorkItemTypeDef
    {
        TfsUtility Utility;
        AutoResetEvent DialogRunning = new AutoResetEvent(false);
        String WITDFileNameForImport;
        bool CancelledOpenWITDFile;

        public WorkItemTypeDef()
        {
            Utility = new TfsUtility();
        }

        /// <summary>
        /// Retrieves all work items in the specified project of the specified type.
        /// </summary>
        /// <param name="sourceProject">The project from which to retrieve work items.</param>
        /// <param name="sourceStore">The WorkItemStore in which the project resides.</param>
        /// <param name="witTypeName">The name of the work item type of which to retrieve.</param>
        /// <returns>The List of WorkItems returned by the query.</returns>
        public static List<WorkItem> GetWorkItemsByType(Project sourceProject, WorkItemStore sourceStore, String witTypeName)
        {
            if (sourceProject == null)
                throw new ArgumentNullException("sourceProject", "sourceProject cannot be null.");

            if (sourceStore == null)
                throw new ArgumentNullException("sourceStore", "sourceStore cannot be null.");

            if (String.IsNullOrEmpty(witTypeName))
                throw new ArgumentNullException("witTypeName", "witTypeName cannot be null.");

            var qText =
            String.Format(@"SELECT [System.Id], [System.WorkItemType], [System.Title], [System.AssignedTo], [System.State] 
                          FROM WorkItems WHERE ([System.TeamProject] = '{0}' AND [Source].[System.WorkItemType] = '{1}')", sourceProject.Name, witTypeName);

            var query = new Query(sourceStore, qText);

            var workItems = query.RunLinkQuery();

            var workItemCollection = new List<WorkItem>();

            foreach (WorkItemLinkInfo workItemlink in workItems)
            {
                workItemCollection.Add(sourceStore.GetWorkItem(workItemlink.TargetId));
            }

            return workItemCollection;
        }


        /// <summary>
        /// Retrieves all work items in the specified project of the specified type.
        /// </summary>
        /// <param name="sourceProject">The project from which to retrieve work items.</param>
        /// <param name="sourceStore">The WorkItemStore in which the project resides.</param>
        /// <param name="filters">The comma-separated list of WITDs used for filtering the returned work items. Only work items 
        /// of types in this argument will be returned. If all work items are desired, filters will have a value of All.</param>
        /// <returns>A List of work items for the specified project and work item store.</returns>
        public static WorkItemCollection GetAllWorkItems(Project sourceProject, WorkItemStore sourceStore, String filters)
        {
            String qText;
            filters = filters.TrimEnd(',');
            if (sourceProject == null)
                throw new ArgumentNullException("sourceProject", "sourceProject cannot be null.");

            if (sourceStore == null)
                throw new ArgumentNullException("sourceStore", "sourceStore cannot be null.");
            if (filters.Contains("All"))
            {
                qText =
                String.Format(@"SELECT [System.Id], [System.WorkItemType], [System.Title], [System.AssignedTo], [System.State] 
                              FROM WorkItems WHERE [System.TeamProject] = {0} ORDER BY [System.Id]", "'" + sourceProject.Name + "'");
            }
            else
            {
                String filterList = "";
                foreach (String filter in filters.Split(','))
                {
                    filterList += "'" + filter + "',";
                }
                filterList = filterList.TrimEnd(',');
                qText =
                String.Format(@"SELECT [System.Id], [System.WorkItemType], [System.Title], [System.AssignedTo], [System.State] 
                              FROM WorkItems WHERE [System.TeamProject] = {0} AND [System.WorkItemType] IN ({1}) ORDER BY [System.Id]", "'" + sourceProject.Name + "'", filterList);
            }

            var query = new Query(sourceStore, qText);

            return query.RunQuery();
        }

        /// <summary>
        /// Determines if a list work item types contains the specified work item type.
        /// </summary>
        /// <param name="wits">The list of work item types to search.</param>
        /// <param name="witToFind">The name of the work item type to search for.</param>
        /// <returns>True if the list contains the specified work item type. Otherwise false.</returns>
        private bool WitInList(ArrayList wits, String witToFind)
        {
            foreach (ImportableWIT wit in wits)
            {
                if (wit.Name.ToUpper() == witToFind.ToUpper())
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Copies work items from a source project to a target project for work item types contained in the specified list.
        /// </summary>
        /// <param name="sourceProject">The project containing the work items to copy.</param>
        /// <param name="targetProject">The project to which to copy the workitems.</param>
        /// <param name="sourceStore">The source project's WorkItemStore.</param>
        /// <param name="targetStore">The target project's WorkItemStore.</param>
        /// <param name="wits">A list of work item types of which to copy work items.</param>
        /// <returns>The number of failed copy attempts.</returns>
        public Int32 CopyWorkItemsBetweenProjects(Project sourceProject, Project targetProject, WorkItemStore sourceStore, WorkItemStore targetStore, ArrayList wits)
        {
            if (sourceProject == null)
                throw new ArgumentNullException("sourceProject", "sourceProject cannot be null.");

            if (targetProject == null)
                throw new ArgumentNullException("targetProject", "targetProject cannot be null.");

            if (sourceStore == null)
                throw new ArgumentNullException("sourceStore", "sourceStore cannot be null.");

            if (targetStore == null)
                throw new ArgumentNullException("targetStore", "targetStore cannot be null.");

            if (wits == null || wits.Count == 0)
                throw new ArgumentNullException("wits", "wits cannot be null or empty.");

            Int32 failures = 0;
            // Query to get the work items that you want to copy
            var qText = String.Format(@"SELECT [System.Id], [System.WorkItemType], [System.Title], [System.AssignedTo], [System.State] 
                           FROM WorkItems WHERE [System.TeamProject] = {0}", "'" + sourceProject.Name + "'");

            var query = new Query(sourceStore, qText);

            var workItems = query.RunQuery();

            // You now have the work item object and all work item property
            // along with attachments, hyperlinks, history, etc 
            foreach (WorkItem workItem in workItems)
            {
                // Create a new instance of work item and copy the 
                // values from the existing work item object
                // this allows you to map the properties across sourceProject 
                // templates
                if (WitInList(wits, workItem.Type.Name))
                {
                    if (targetStore.Projects[targetProject.Name].WorkItemTypes.Contains(workItem.Type.Name))
                    {
                        WorkItem newWorkItem = targetStore.Projects[targetProject.Name].WorkItemTypes[workItem.Type.Name].NewWorkItem();

                        foreach (Field fld in workItem.Fields)
                        {
                            if (newWorkItem.Fields.Contains(fld.Name))
                            {
                                if (fld.Name != "Team Project" && fld.Name != "Iteration ID" && fld.Name != "Area ID")
                                {
                                    if (newWorkItem.Fields[fld.Name].IsEditable) { newWorkItem.Fields[fld.Name].Value = fld.Value; }
                                }
                            }
                        }

                        newWorkItem.Description = workItem.Description;
                        newWorkItem.History = workItem.History;
                        newWorkItem.Title = workItem.Title;
                        newWorkItem.State = "Proposed";
                        newWorkItem.AreaPath = targetProject.Name;
                        newWorkItem.IterationPath = targetStore.Projects[targetProject.Name].IterationRootNodes[0].Path;
                        newWorkItem.Reason = "New";

                        // Validate the work item
                        ArrayList ValidationResult = newWorkItem.Validate();

                        if (newWorkItem.IsValid())
                        {
                            newWorkItem.Save(SaveFlags.None);
                        }
                        else
                        {
                            foreach (Field val in ValidationResult)
                            {
                                if (val.HasAllowedValuesList && val.AllowedValues.Count > 0)
                                {
                                    val.Value = val.AllowedValues[0];
                                }
                            }
                            if (newWorkItem.IsValid()) { newWorkItem.Save(); } else { failures++; }
                        }
                    }
                }
            }

            return failures;
        }


        /// <summary>
        /// Attempts to rename a work item type.
        /// </summary>
        /// <param name="uri">The collection Uri</param>
        /// <param name="projectName">The name of the project that contains the work item type to rename.</param>
        /// <param name="currentName">The current name of the work item type to rename.</param>
        /// <param name="newName">The proposed new name of the work item type.</param>
        /// <param name="message">Stores any error messages associated with this method call.</param>
        public void RenameWITD(String uri, String projectName, String currentName, String newName, out String message)
        {
            message = "";
            if (Utility.WitAdminFound)
            {
                String commandLine = String.Format("renamewitd /collection:{0} /p:\"{1}\" /n:\"{2}\" /new:\"{3}\" /noprompt", uri, projectName, currentName, newName);

                int result = Utility.RunProcess(Utility.WitCommandName, commandLine);
                if (result != 0)
                {
                    //error
                    //get results from process
                    message = Utility.ErrorMessage;
                }
            }
            else
                message = "WitAdmin was not found.";
        }

        /// <summary>
        /// Changes the name Attribute of the work item type Xml.
        /// </summary>
        /// <param name="wit">The work item type to rename.</param>
        /// <param name="newName">The proposed new name of the work item type.</param>
        /// <param name="message">Stores any error messages associated with this method call.</param>
        public XmlDocument RenameWITD(ImportableWIT wit, String newName, out String message)
        {
            XmlDocument xml = wit.Definition;
            message = "";

            XmlNodeList witTypeNodes = xml.GetElementsByTagName("WORKITEMTYPE");
            if (witTypeNodes.Count > 0)
            {
                witTypeNodes[0].Attributes["name"].Value = newName;
            }

            return xml;
        }

        /// <summary>
        /// Attempts to destroy the specified work item type from the specified project.
        /// </summary>
        /// <param name="uri">The collection uri.</param>
        /// <param name="projectName">The name of the project that contains the work item type to destroy.</param>
        /// <param name="witName">The name of the work item type to destroy.</param>
        /// <param name="message">Stores any error messages associated with this method call.</param>
        public void DestroyWITD(String uri, String projectName, String witName, out String message)
        {
            int exitCode;
            message = "";

            try
            {
                String commandLine = String.Format("destroywitd /collection:{0} /p:\"{1}\" /n:\"{2}\" /noprompt", uri, projectName, witName);

                exitCode = Utility.RunProcess(Utility.WitCommandName, commandLine);
                if (exitCode != 0)
                {
                    message = Utility.ErrorMessage + Environment.NewLine +
                              "Check the categories for this project. If " + witName + " is the default work item type for one or more categories, then it must be removed " +
                              "or made a non-default work item type.";
                }

            }
            catch (Exception ex)
            {
                message = "Error destroying " + witName + " in project " + projectName + ": " + ex.Message;
            }
        }

        /// <summary>
        /// Calls the Rebuildcache command in WitAdmin for t he specified collection uri.
        /// </summary>
        /// <param name="uri">The collection uri.</param>
        public void Refresh(String uri)
        {
            String commandLine = String.Format("rebuildcache /collection:{0} /noprompt", uri);
            Utility.RunProcess(Utility.WitCommandName, commandLine);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="project"></param>
        /// <param name="validateOnly">True to validate the work item type definition only. Otherwise validates and imports the work item type definition.</param>
        /// <param name="message">Stores any error messages to give back to caller.</param>
        public void ImportWITDFromFile(TfsTeamProjectCollection collection, Project project, bool validateOnly, out String message)
        {
            bool failed = false;
            message = "";

            OpenWITDFile();

            if (!CancelledOpenWITDFile)
            {
                XmlDocument xml = new XmlDocument();
                xml.Load(WITDFileNameForImport);

                //always do validation before import
                try
                {
                    WorkItemType.Validate(project, xml.DocumentElement.InnerXml);

                }
                catch (XmlException ex)
                {
                    message = "Error validating " + WITDFileNameForImport + ": " + ex.Message;
                    failed = true;
                }
                catch (XmlSchemaValidationException sex)
                {
                    message = "Error validating " + WITDFileNameForImport + ": " + sex.Message;
                    failed = true;
                }

                if (!failed && !validateOnly)
                {
                    try
                    {
                        project.WorkItemTypes.Import(xml.DocumentElement);
                    }
                    catch (Exception ex)
                    {
                        message = "Error importing " + WITDFileNameForImport + ": " + ex.Message;
                    }
                }
            }
        }

        /// <summary>
        /// Opens an <see cref="OpenFileDialog">OpenFileDialog</see> to let the user choose a Work Item Type Definition file to import.
        /// </summary>        
        [STAThread]
        public void FileDialog()
        {
            OpenFileDialog dialog = new OpenFileDialog();

            dialog.Title = "Select work item type definition file for import";
            dialog.DefaultExt = "xml";
            dialog.Filter = "Work Item Type Definition File (*.xml)|*.xml";
            dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            dialog.CheckFileExists = false;
            dialog.RestoreDirectory = true;
            dialog.Multiselect = false;
            dialog.SupportMultiDottedExtensions = true;
            CancelledOpenWITDFile = false;

            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                WITDFileNameForImport = dialog.FileName;
            }
            else
                CancelledOpenWITDFile = true;

            if (dialog != null) { dialog.Dispose(); }
            DialogRunning.Set();
        }

        /// <summary>
        /// Entry point for saving the global list file. Must be set to STA thread. There does not seem to be a way to alter the owning thread to STA in WPF, so 
        /// in this manner a new thread can be created as STA and managed via AutoResetEvent object to signal.
        /// </summary>
        [STAThread()]
        public void OpenWITDFile()
        {
            System.Threading.Thread openFile = new Thread(FileDialog);
            openFile.SetApartmentState(ApartmentState.STA);
            openFile.Start();

            DialogRunning.WaitOne();
        }
    }
}
